home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / HTTPREQUEST.PY < prev    next >
Encoding:
Python Source  |  2000-08-15  |  40.0 KB  |  1,045 lines

  1. ##############################################################################
  2. # Zope Public License (ZPL) Version 1.0
  3. # -------------------------------------
  4. # Copyright (c) Digital Creations.  All rights reserved.
  5. # This license has been certified as Open Source(tm).
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions are
  8. # met:
  9. # 1. Redistributions in source code must retain the above copyright
  10. #    notice, this list of conditions, and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. #    notice, this list of conditions, and the following disclaimer in
  13. #    the documentation and/or other materials provided with the
  14. #    distribution.
  15. # 3. Digital Creations requests that attribution be given to Zope
  16. #    in any manner possible. Zope includes a "Powered by Zope"
  17. #    button that is installed by default. While it is not a license
  18. #    violation to remove this button, it is requested that the
  19. #    attribution remain. A significant investment has been put
  20. #    into Zope, and this effort will continue if the Zope community
  21. #    continues to grow. This is one way to assure that growth.
  22. # 4. All advertising materials and documentation mentioning
  23. #    features derived from or use of this software must display
  24. #    the following acknowledgement:
  25. #      "This product includes software developed by Digital Creations
  26. #      for use in the Z Object Publishing Environment
  27. #      (http://www.zope.org/)."
  28. #    In the event that the product being advertised includes an
  29. #    intact Zope distribution (with copyright and license included)
  30. #    then this clause is waived.
  31. # 5. Names associated with Zope or Digital Creations must not be used to
  32. #    endorse or promote products derived from this software without
  33. #    prior written permission from Digital Creations.
  34. # 6. Modified redistributions of any form whatsoever must retain
  35. #    the following acknowledgment:
  36. #      "This product includes software developed by Digital Creations
  37. #      for use in the Z Object Publishing Environment
  38. #      (http://www.zope.org/)."
  39. #    Intact (re-)distributions of any official Zope release do not
  40. #    require an external acknowledgement.
  41. # 7. Modifications are encouraged but must be packaged separately as
  42. #    patches to official Zope releases.  Distributions that do not
  43. #    clearly separate the patches from the original work must be clearly
  44. #    labeled as unofficial distributions.  Modifications which do not
  45. #    carry the name Zope may be packaged in any form, as long as they
  46. #    conform to all of the clauses above.
  47. # Disclaimer
  48. #   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
  49. #   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  50. #   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  51. #   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
  52. #   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  53. #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  54. #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  55. #   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  56. #   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  57. #   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  58. #   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  59. #   SUCH DAMAGE.
  60. # This software consists of contributions made by Digital Creations and
  61. # many individuals on behalf of Digital Creations.  Specific
  62. # attributions are listed in the accompanying credits file.
  63. ##############################################################################
  64.  
  65. __version__='$Revision: 1.35.2.2 $'[11:-2]
  66.  
  67. import regex, sys, os, string, urllib
  68. from string import lower, atoi, rfind, split, strip, join, upper, find
  69. from BaseRequest import BaseRequest
  70. from HTTPResponse import HTTPResponse
  71. from cgi import FieldStorage
  72. from urllib import quote, unquote, splittype, splitport
  73. from Converters import get_converter
  74. from maybe_lock import allocate_lock
  75. xmlrpc=None # Placeholder for module that we'll import if we have to.
  76.  
  77. isCGI_NAME = {
  78.         'SERVER_SOFTWARE' : 1, 
  79.         'SERVER_NAME' : 1, 
  80.         'GATEWAY_INTERFACE' : 1, 
  81.         'SERVER_PROTOCOL' : 1, 
  82.         'SERVER_PORT' : 1, 
  83.         'REQUEST_METHOD' : 1, 
  84.         'PATH_INFO' : 1, 
  85.         'PATH_TRANSLATED' : 1, 
  86.         'SCRIPT_NAME' : 1, 
  87.         'QUERY_STRING' : 1, 
  88.         'REMOTE_HOST' : 1, 
  89.         'REMOTE_ADDR' : 1, 
  90.         'AUTH_TYPE' : 1, 
  91.         'REMOTE_USER' : 1, 
  92.         'REMOTE_IDENT' : 1, 
  93.         'CONTENT_TYPE' : 1, 
  94.         'CONTENT_LENGTH' : 1,
  95.         'SERVER_URL': 1,
  96.         }.has_key
  97.  
  98. hide_key={'HTTP_AUTHORIZATION':1,
  99.           'HTTP_CGI_AUTHORIZATION': 1,
  100.           }.has_key
  101.  
  102. default_port={'http': '80', 'https': '443'}
  103.  
  104. _marker=[]
  105. class HTTPRequest(BaseRequest):
  106.     """\
  107.     Model HTTP request data.
  108.     
  109.     This object provides access to request data.  This includes, the
  110.     input headers, form data, server data, and cookies.
  111.  
  112.     Request objects are created by the object publisher and will be
  113.     passed to published objects through the argument name, REQUEST.
  114.  
  115.     The request object is a mapping object that represents a
  116.     collection of variable to value mappings.  In addition, variables
  117.     are divided into four categories:
  118.  
  119.       - Environment variables
  120.  
  121.         These variables include input headers, server data, and other
  122.         request-related data.  The variable names are as <a
  123.         href="http://hoohoo.ncsa.uiuc.edu/cgi/env.html">specified</a>
  124.         in the <a
  125.         href="http://hoohoo.ncsa.uiuc.edu/cgi/interface.html">CGI
  126.         specification</a>
  127.  
  128.       - Form data
  129.  
  130.         These are data extracted from either a URL-encoded query
  131.         string or body, if present.
  132.  
  133.       - Cookies
  134.  
  135.         These are the cookie data, if present.
  136.  
  137.       - Other
  138.  
  139.         Data that may be set by an application object.
  140.  
  141.     The form attribute of a request is actually a Field Storage
  142.     object.  When file uploads are used, this provides a richer and
  143.     more complex interface than is provided by accessing form data as
  144.     items of the request.  See the FieldStorage class documentation
  145.     for more details.
  146.  
  147.     The request object may be used as a mapping object, in which case
  148.     values will be looked up in the order: environment variables,
  149.     other variables, form data, and then cookies.
  150.     """
  151.     _hacked_path=None
  152.     args=()
  153.     _file=None
  154.     _urls = ()
  155.  
  156.     retry_max_count=3
  157.     def supports_retry(self): return self.retry_count < self.retry_max_count
  158.  
  159.     def retry(self):
  160.         self.retry_count=self.retry_count+1
  161.         self.stdin.seek(0)
  162.         r=self.__class__(stdin=self.stdin,
  163.                          environ=self._orig_env,
  164.                          response=self.response.retry()
  165.                          )
  166.         r.retry_count=self.retry_count
  167.         return r
  168.  
  169.  
  170.     def setServerURL(self, protocol=None, hostname=None, port=None):
  171.         """ Set the parts of generated URLs. """
  172.         other = self.other
  173.         server_url = other.get('SERVER_URL', '')
  174.         if protocol is None and hostname is None and port is None:
  175.             return server_url
  176.         oldprotocol, oldhost = splittype(server_url)
  177.         oldhostname, oldport = splitport(oldhost[2:])
  178.         if protocol is None: protocol = oldprotocol
  179.         if hostname is None: hostname = oldhostname
  180.         if port is None: port = oldport
  181.         
  182.         if (port is None or default_port[protocol] == port):
  183.             host = hostname
  184.         else:
  185.             host = hostname + ':' + port
  186.         server_url = other['SERVER_URL'] = '%s://%s' % (protocol, host)
  187.         self._resetURLS()
  188.         return server_url
  189.  
  190.     def setVirtualRoot(self, path, hard=0):
  191.         """ Treat the current publishing object as a VirtualRoot """
  192.         other = self.other
  193.         if type(path) is type(''):
  194.             path = filter(None, split(path, '/'))
  195.         self._script[:] = map(quote, path)
  196.         del self._steps[:]
  197.         parents = other['PARENTS']
  198.         if hard:
  199.             del parents[:-1]
  200.         other['VirtualRootPhysicalPath'] = parents[-1].getPhysicalPath()
  201.         self._resetURLS()
  202.  
  203.     def _resetURLS(self):
  204.         other = self.other
  205.         other['URL'] = join([other['SERVER_URL']] + self._script +
  206.                             self._steps, '/')
  207.         for x in self._urls:
  208.             del self.other[x]
  209.         self._urls = ()
  210.  
  211.     def __init__(self, stdin, environ, response, clean=0):
  212.         self._orig_env=environ
  213.         # Avoid the overhead of scrubbing the environment in the
  214.         # case of request cloning for traversal purposes. If the
  215.         # clean flag is set, we know we can use the passed in
  216.         # environ dict directly.
  217.         if not clean: environ=sane_environment(environ)
  218.  
  219.         if environ.has_key('HTTP_AUTHORIZATION'):
  220.             self._auth=environ['HTTP_AUTHORIZATION']
  221.             response._auth=1
  222.             del environ['HTTP_AUTHORIZATION']
  223.  
  224.         self.stdin=stdin
  225.         self.environ=environ
  226.         have_env=environ.has_key
  227.         get_env=environ.get
  228.         self.response=response
  229.         other=self.other={'RESPONSE': response}
  230.         self.form={}
  231.         self.steps=[]
  232.         self._steps=[]
  233.  
  234.         ################################################################
  235.         # Get base info first. This isn't likely to cause
  236.         # errors and might be useful to error handlers.
  237.         b=script=strip(get_env('SCRIPT_NAME',''))
  238.  
  239.         # _script and the other _names are meant for URL construction
  240.         self._script = map(quote, filter(None, split(script, '/')))
  241.         
  242.         while b and b[-1]=='/': b=b[:-1]
  243.         p = rfind(b,'/')
  244.         if p >= 0: b=b[:p+1]
  245.         else: b=''
  246.         while b and b[0]=='/': b=b[1:]
  247.  
  248.         server_url=get_env('SERVER_URL',None)
  249.         if server_url is not None:
  250.              other['SERVER_URL'] = server_url = strip(server_url)
  251.         else:
  252.              if have_env('HTTPS') and (
  253.                  environ['HTTPS'] == "on" or environ['HTTPS'] == "ON"):
  254.                  protocol = 'https'
  255.              elif (have_env('SERVER_PORT_SECURE') and 
  256.                    environ['SERVER_PORT_SECURE'] == "1"):
  257.                  protocol = 'https'
  258.              else: protocol = 'http'
  259.  
  260.              if have_env('HTTP_HOST'):
  261.                  host = strip(environ['HTTP_HOST'])
  262.                  hostname, port = splitport(host)
  263.              else:
  264.                  hostname = strip(environ['SERVER_NAME'])
  265.                  port = environ['SERVER_PORT']
  266.              self.setServerURL(protocol=protocol, hostname=hostname, port=port)
  267.              server_url = other['SERVER_URL']
  268.              
  269.         if server_url[-1:]=='/': server_url=server_url[:-1]
  270.                         
  271.         if b: self.base="%s/%s" % (server_url,b)
  272.         else: self.base=server_url
  273.         while script[:1]=='/': script=script[1:]
  274.         if script: script="%s/%s" % (server_url,script)
  275.         else:      script=server_url
  276.         other['URL']=self.script=script
  277.  
  278.         ################################################################
  279.         # Cookie values should *not* be appended to existing form
  280.         # vars with the same name - they are more like default values
  281.         # for names not otherwise specified in the form.
  282.         cookies={}
  283.         k=get_env('HTTP_COOKIE','')
  284.         if k:
  285.             parse_cookie(k, cookies)
  286.             for k,item in cookies.items():
  287.                 if not other.has_key(k):
  288.                     other[k]=item
  289.         self.cookies=cookies
  290.     
  291.     def processInputs(
  292.         self,
  293.         # "static" variables that we want to be local for speed
  294.         SEQUENCE=1,
  295.         DEFAULT=2,
  296.         RECORD=4,
  297.         RECORDS=8,
  298.         REC=12, # RECORD|RECORDS
  299.         EMPTY=16,
  300.         CONVERTED=32,
  301.         hasattr=hasattr,
  302.         getattr=getattr,
  303.         setattr=setattr,
  304.         search_type=regex.compile(':[a-zA-Z][a-zA-Z0-9_]+$').search,
  305.         rfind=string.rfind,
  306.         ):
  307.         """Process request inputs
  308.  
  309.         We need to delay input parsing so that it is done under
  310.         publisher control for error handling purposes.
  311.         """
  312.         response=self.response
  313.         environ=self.environ
  314.         method=environ.get('REQUEST_METHOD','GET')
  315.         
  316.         if method != 'GET': fp=self.stdin
  317.         else:               fp=None
  318.  
  319.         form=self.form
  320.         other=self.other
  321.  
  322.         meth=None
  323.         fs=FieldStorage(fp=fp,environ=environ,keep_blank_values=1)
  324.         if not hasattr(fs,'list') or fs.list is None:
  325.             # Hm, maybe it's an XML-RPC
  326.             if (fs.headers.has_key('content-type') and
  327.                 fs.headers['content-type'] == 'text/xml' and
  328.                 method == 'POST'):
  329.                 # Ye haaa, XML-RPC!
  330.                 global xmlrpc
  331.                 if xmlrpc is None: import xmlrpc
  332.                 meth, self.args = xmlrpc.parse_input(fs.value)
  333.                 response=xmlrpc.response(response)
  334.                 other['RESPONSE']=self.response=response
  335.                 other['REQUEST_METHOD']='' # We don't want index_html!
  336.                 self.maybe_webdav_client = 0
  337.             else:
  338.                 self._file=fs.file
  339.         else:
  340.             fslist=fs.list
  341.             tuple_items={}
  342.             lt=type([])
  343.             CGI_name=isCGI_NAME
  344.             defaults={}
  345.             converter=seqf=None
  346.             
  347.             for item in fslist:
  348.                 
  349.                 key=item.name
  350.                 if (hasattr(item,'file') and hasattr(item,'filename')
  351.                     and hasattr(item,'headers')):
  352.                     if (item.file and
  353.                         (item.filename is not None
  354.                          # RFC 1867 says that all fields get a content-type.
  355.                          # or 'content-type' in map(lower, item.headers.keys())
  356.                          )):
  357.                         item=FileUpload(item)
  358.                     else:
  359.                         item=item.value
  360.  
  361.                 flags=0
  362.  
  363.                 # Loop through the different types and set
  364.                 # the appropriate flags
  365.  
  366.                 # We'll search from the back to the front.
  367.                 # We'll do the search in two steps.  First, we'll
  368.                 # do a string search, and then we'll check it with
  369.                 # a regex search.
  370.                 
  371.                 l=rfind(key,':')
  372.                 if l >= 0:
  373.                     l=search_type(key,l)
  374.                     while l >= 0:
  375.                         type_name=key[l+1:]
  376.                         key=key[:l]
  377.                         c=get_converter(type_name, None)
  378.                         if c is not None: 
  379.                             converter=c
  380.                             flags=flags|CONVERTED
  381.                         elif type_name == 'list':
  382.                             seqf=list
  383.                             flags=flags|SEQUENCE
  384.                         elif type_name == 'tuple':
  385.                             seqf=tuple
  386.                             tuple_items[key]=1
  387.                             flags=flags|SEQUENCE
  388.                         elif type_name == 'method':
  389.                             if l: meth=key
  390.                             else: meth=item
  391.                         elif type_name == 'default_method':
  392.                             if not meth:
  393.                                 if l: meth=key
  394.                                 else: meth=item
  395.                         elif type_name == 'default':
  396.                             flags=flags|DEFAULT
  397.                         elif type_name == 'record':
  398.                             flags=flags|RECORD
  399.                         elif type_name == 'records':
  400.                             flags=flags|RECORDS
  401.                         elif type_name == 'ignore_empty':
  402.                             if not item: flags=flags|EMPTY
  403.     
  404.                         l=rfind(key,':')
  405.                         if l < 0: break
  406.                         l=search_type(key,l)
  407.              
  408.                 # Filter out special names from form:
  409.                 if CGI_name(key) or key[:5]=='HTTP_': continue
  410.  
  411.                 if flags:
  412.  
  413.                     # skip over empty fields    
  414.                     if flags&EMPTY: continue
  415.  
  416.                     #Split the key and its attribute
  417.                     if flags&REC:
  418.                         key=split(key,".")
  419.                         key, attr=join(key[:-1],"."), key[-1]
  420.                        
  421.                     # defer conversion
  422.                     if flags&CONVERTED:
  423.                         try:
  424.                             item=converter(item)
  425.                         except:
  426.                             if (not item and not (flags&DEFAULT) and
  427.                                 defaults.has_key(key)):
  428.                                 item = defaults[key]
  429.                                 if flags&RECORD:
  430.                                     item=getattr(item,attr)
  431.                                 if flags&RECORDS:
  432.                                     item.reverse()
  433.                                     item = item[0]
  434.                                     item=getattr(item,attr)
  435.                             else:
  436.                                 raise                            
  437.                          
  438.                     #Determine which dictionary to use
  439.                     if flags&DEFAULT:
  440.                        mapping_object = defaults
  441.                     else:
  442.                        mapping_object = form
  443.  
  444.                     #Insert in dictionary
  445.                     if mapping_object.has_key(key):
  446.                        if flags&RECORDS:
  447.                            #Get the list and the last record
  448.                            #in the list
  449.                            reclist = mapping_object[key]
  450.                            reclist.reverse()
  451.                            x=reclist[0]
  452.                            reclist.reverse()
  453.                            if not hasattr(x,attr):
  454.                                #If the attribute does not
  455.                                #exist, setit
  456.                                if flags&SEQUENCE: item=[item]
  457.                                reclist.remove(x)
  458.                                setattr(x,attr,item)
  459.                                reclist.append(x)
  460.                                mapping_object[key] = reclist
  461.                            else:
  462.                                if flags&SEQUENCE:
  463.                                    # If the attribute is a
  464.                                    # sequence, append the item
  465.                                    # to the existing attribute
  466.                                    reclist.remove(x)
  467.                                    y = getattr(x, attr)
  468.                                    y.append(item)
  469.                                    setattr(x, attr, y)
  470.                                    reclist.append(x)
  471.                                    mapping_object[key] = reclist
  472.                                else:
  473.                                    # Create a new record and add
  474.                                    # it to the list
  475.                                    n=record()
  476.                                    setattr(n,attr,item)
  477.                                    reclist.append(n)
  478.                                    mapping_object[key]=reclist
  479.                        elif flags&RECORD:
  480.                            b=mapping_object[key]
  481.                            if flags&SEQUENCE:
  482.                               item=[item]
  483.                               if not hasattr(b,attr):
  484.                                   # if it does not have the
  485.                                   # attribute, set it
  486.                                   setattr(b,attr,item)
  487.                               else:
  488.                                   # it has the attribute so
  489.                                   # append the item to it
  490.                                   setattr(b,attr,getattr(b,attr)+item)
  491.                            else:
  492.                               # it is not a sequence so
  493.                               # set the attribute
  494.                               setattr(b,attr,item)        
  495.                        else:
  496.                           # it is not a record or list of records
  497.                            found=mapping_object[key]
  498.                            if type(found) is lt:
  499.                                found.append(item)
  500.                            else:
  501.                                found=[found,item]
  502.                                mapping_object[key]=found
  503.                     else:
  504.                        # The dictionary does not have the key
  505.                        if flags&RECORDS:
  506.                            # Create a new record, set its attribute
  507.                            # and put it in the dictionary as a list
  508.                            a = record()
  509.                            if flags&SEQUENCE: item=[item]
  510.                            setattr(a,attr,item)
  511.                            mapping_object[key]=[a]
  512.                        elif flags&RECORD:
  513.                            # Create a new record, set its attribute
  514.                            # and put it in the dictionary
  515.                            if flags&SEQUENCE: item=[item]
  516.                            r = mapping_object[key]=record()
  517.                            setattr(r,attr,item)
  518.                        else:
  519.                            # it is not a record or list of records
  520.                            if flags&SEQUENCE: item=[item]
  521.                            mapping_object[key]=item
  522.  
  523.                 else:
  524.                     # This branch is for case when no type was specified.
  525.                     mapping_object = form
  526.     
  527.                     #Insert in dictionary
  528.                     if mapping_object.has_key(key):
  529.                         # it is not a record or list of records
  530.                         found=mapping_object[key]
  531.                         if type(found) is lt:
  532.                             found.append(item)
  533.                         else:
  534.                             found=[found,item]
  535.                             mapping_object[key]=found
  536.                     else:
  537.                         mapping_object[key]=item
  538.  
  539.             #insert defaults into form dictionary
  540.             if defaults:
  541.                 for keys, values in defaults.items():
  542.                     if not form.has_key(keys):
  543.                         # if the form does not have the key,
  544.                         # set the default
  545.                         form[keys]=values
  546.                     else:
  547.                         #The form has the key
  548.                         if getattr(values, '__class__',0) is record:
  549.                            # if the key is mapped to a record, get the
  550.                            # record
  551.                            r = form[keys]
  552.                            for k, v in values.__dict__.items():
  553.                               # loop through the attributes and values
  554.                               # in the default dictionary
  555.                               if not hasattr(r, k):
  556.                                  # if the form dictionary doesn't have
  557.                                  # the attribute, set it to the default
  558.                                  setattr(r,k,v)
  559.                                  form[keys] = r    
  560.                         elif values == type([]):
  561.                                # the key is mapped to a list
  562.                                l = form[keys]
  563.                                for x in values:
  564.                                    # for each x in the list
  565.                                    if getattr(x, '__class__',0) is record:
  566.                                        # if the x is a record
  567.                                        for k, v in x.__dict__.items():
  568.                                            
  569.                                            # loop through each
  570.                                            # attribute and value in
  571.                                            # the record
  572.                                            
  573.                                            for y in l:
  574.                                                
  575.                                                # loop through each
  576.                                                # record in the form
  577.                                                # list if it doesn't
  578.                                                # have the attributes
  579.                                                # in the default
  580.                                                # dictionary, set them
  581.                                                
  582.                                                if not hasattr(y, k):
  583.                                                    setattr(y, k, v)
  584.                                    else:
  585.                                        # x is not a record
  586.                                        if not a in l:
  587.                                            l.append(a)
  588.                                form[keys] = l
  589.                         else:
  590.                             # The form has the key, the key is not mapped
  591.                             # to a record or sequence so do nothing
  592.                             pass
  593.                                 
  594.             # Convert to tuples
  595.             if tuple_items:
  596.                 for key in tuple_items.keys():
  597.                    # Split the key and get the attr
  598.                    k=split(key, ".")
  599.                    k,attr=join(k[:-1], "."), k[-1]
  600.                    a = attr
  601.                    # remove any type_names in the attr
  602.                    while not a=='':
  603.                       a=split(a, ":")
  604.                       a,new=join(a[:-1], ":"), a[-1]
  605.                    attr = new
  606.                    if form.has_key(k):
  607.                       # If the form has the split key get its value
  608.                       item =form[k]
  609.                       if (hasattr(item, '__class__') and
  610.                           item.__class__ is record):
  611.                          # if the value is mapped to a record, check if it
  612.                          # has the attribute, if it has it, convert it to
  613.                          # a tuple and set it
  614.                          if hasattr(item,attr):
  615.                             value=tuple(getattr(item,attr))
  616.                             setattr(item,attr,value)
  617.                       else:
  618.                          # It is mapped to a list of  records
  619.                          for x in item:
  620.                             # loop through the records
  621.                             if hasattr(x, attr):
  622.                                # If the record has the attribute
  623.                                # convert it to a tuple and set it
  624.                                value=tuple(getattr(x,attr))
  625.                                setattr(x,attr,value)          
  626.                    else:
  627.                       # the form does not have the split key 
  628.                       if form.has_key(key):
  629.                          # if it has the original key, get the item
  630.                          # convert it to a tuple
  631.                          item=form[key]  
  632.                          item=tuple(form[key])
  633.                          form[key]=item
  634.                      
  635.         other.update(form)
  636.         if meth:
  637.             if environ.has_key('PATH_INFO'):
  638.                 path=environ['PATH_INFO']
  639.                 while path[-1:]=='/': path=path[:-1]
  640.             else: path=''
  641.             other['PATH_INFO']=path="%s/%s" % (path,meth)
  642.             self._hacked_path=1
  643.  
  644.     def resolve_url(self, url):
  645.         # Attempt to resolve a url into an object in the Zope
  646.         # namespace. The url must be a fully-qualified url. The
  647.         # method will return the requested object if it is found
  648.         # or raise the same HTTP error that would be raised in
  649.         # the case of a real web request. If the passed in url
  650.         # does not appear to describe an object in the system
  651.         # namespace (e.g. the host, port or script name dont
  652.         # match that of the current request), a ValueError will
  653.         # be raised.
  654.         if find(url, self.script) != 0:
  655.             raise ValueError, 'Different namespace.'
  656.         path=url[len(self.script):]
  657.         while path and path[0]=='/':  path=path[1:]
  658.         while path and path[-1]=='/': path=path[:-1]
  659.         req=self.clone()
  660.         rsp=req.response
  661.         req['PATH_INFO']=path
  662.         object=None
  663.         
  664.         # Try to traverse to get an object. Note that we call
  665.         # the exception method on the response, but we don't
  666.         # want to actually abort the current transaction
  667.         # (which is usually the default when the exception
  668.         # method is called on the response).
  669.         try: object=req.traverse(path)
  670.         except: rsp.exception()
  671.         if object is None:
  672.             req.close()
  673.             raise rsp.errmsg, sys.exc_info()[1]
  674.  
  675.         # The traversal machinery may return a "default object"
  676.         # like an index_html document. This is not appropriate
  677.         # in the context of the resolve_url method so we need
  678.         # to ensure we are getting the actual object named by
  679.         # the given url, and not some kind of default object.
  680.         if hasattr(object, 'id'):
  681.             if callable(object.id):
  682.                 name=object.id()
  683.             else: name=object.id
  684.         elif hasattr(object, '__name__'):
  685.             name=object.__name__
  686.         else: name=''
  687.         if name != os.path.split(path)[-1]:
  688.             object=req.PARENTS[0]
  689.  
  690.         req.close()
  691.         return object
  692.         
  693.  
  694.     def clone(self):
  695.         # Return a clone of the current request object 
  696.         # that may be used to perform object traversal.
  697.         environ=self.environ.copy()
  698.         environ['REQUEST_METHOD']='GET'
  699.         if self._auth: environ['HTTP_AUTHORIZATION']=self._auth
  700.         clone=HTTPRequest(None, environ, HTTPResponse(), clean=1)
  701.         clone['PARENTS']=[self['PARENTS'][-1]]
  702.         return clone
  703.  
  704.     def get_header(self, name, default=None):
  705.         """Return the named HTTP header, or an optional default
  706.         argument or None if the header is not found. Note that
  707.         both original and CGI-ified header names are recognized,
  708.         e.g. 'Content-Type', 'CONTENT_TYPE' and 'HTTP_CONTENT_TYPE'
  709.         should all return the Content-Type header, if available.
  710.         """
  711.         environ=self.environ
  712.         name=upper(join(split(name,"-"),"_"))
  713.         val=environ.get(name, None)
  714.         if val is not None:
  715.             return val
  716.         if name[:5] != 'HTTP_':
  717.             name='HTTP_%s' % name
  718.         return environ.get(name, default)
  719.  
  720.  
  721.     def __getitem__(self,key,
  722.                     default=_marker, # Any special internal marker will do
  723.                     URLmatch=regex.compile('URL[0-9]+$').match,
  724.                     BASEmatch=regex.compile('BASE[0-9]+$').match,
  725.                     ):
  726.         """Get a variable value
  727.  
  728.         Return a value for the required variable name.
  729.         The value will be looked up from one of the request data
  730.         categories. The search order is environment variables,
  731.         other variables, form data, and then cookies. 
  732.         
  733.         """ #"
  734.         other=self.other
  735.         if other.has_key(key):
  736.             if key=='REQUEST': return self
  737.             return other[key]
  738.  
  739.         if key[:1]=='U' and URLmatch(key) >= 0:
  740.             path = self._script + self._steps
  741.             n = len(path) - atoi(key[3:])
  742.             if n < 0:
  743.                 raise KeyError, key
  744.             URL=join([other['SERVER_URL']] + path[:n], '/')
  745.             other[key]=URL
  746.             self._urls = self._urls + (key,)
  747.             return URL
  748.  
  749.         if isCGI_NAME(key) or key[:5] == 'HTTP_':
  750.             environ=self.environ
  751.             if environ.has_key(key) and (not hide_key(key)):
  752.                 return environ[key]
  753.             return ''
  754.  
  755.         if key=='REQUEST': return self
  756.  
  757.         if key[:1]=='B':
  758.             if BASEmatch(key) >= 0:
  759.                 path = self._steps
  760.                 n = atoi(key[4:])
  761.                 if n:
  762.                     n = n - 1
  763.                     if len(path) < n:
  764.                         raise KeyError, key
  765.  
  766.                     v = self._script + path[:n]
  767.                 else:
  768.                     v = self._script[:-1]
  769.                 other[key] = v = join([other['SERVER_URL']] + v, '/')
  770.                 self._urls = self._urls + (key,)
  771.                 return v
  772.  
  773.             if key=='BODY' and self._file is not None:
  774.                 p=self._file.tell()
  775.                 self._file.seek(0)
  776.                 v=self._file.read()
  777.                 self._file.seek(p)
  778.                 self.other[key]=v
  779.                 return v
  780.  
  781.             if key=='BODYFILE' and self._file is not None:
  782.                 v=self._file
  783.                 self.other[key]=v
  784.                 return v
  785.  
  786.         v=self.common.get(key, default)
  787.         if v is not _marker: return v
  788.  
  789.         raise KeyError, key
  790.  
  791.     __getattr__=__getitem__
  792.  
  793.     def get(self, key, default=None):
  794.         return self.__getitem__(key, default)
  795.  
  796.     def has_key(self, key):
  797.         try: self[key]
  798.         except: return 0
  799.         else: return 1
  800.  
  801.     def keys(self):
  802.         keys = {}
  803.         keys.update(self.common)
  804.  
  805.         for key in self.environ.keys():
  806.             if (isCGI_NAME(key) or key[:5] == 'HTTP_') and \
  807.                (not hide_key(key)):
  808.                     keys[key] = 1
  809.  
  810.         n=0
  811.         while 1:
  812.             n=n+1
  813.             key = "URL%s" % n
  814.             if not self.has_key(key): break
  815.  
  816.         n=0
  817.         while 1:
  818.             n=n+1
  819.             key = "BASE%s" % n
  820.             if not self.has_key(key): break
  821.  
  822.         keys.update(self.other)
  823.  
  824.         keys=keys.keys()
  825.         keys.sort()
  826.  
  827.         return keys
  828.  
  829.     def __str__(self):
  830.         result="<h3>form</h3><table>"
  831.         row='<tr valign="top" align="left"><th>%s</th><td>%s</td></tr>'
  832.         for k,v in self.form.items():
  833.             result=result + row % (html_quote(k), html_quote(v))
  834.         result=result+"</table><h3>cookies</h3><table>"
  835.         for k,v in self.cookies.items():
  836.             result=result + row % (html_quote(k), html_quote(v))
  837.         result=result+"</table><h3>other</h3><table>"
  838.         for k,v in self.other.items():
  839.             if k in ('PARENTS','RESPONSE'): continue
  840.             result=result + row % (html_quote(k), html_quote(v))
  841.     
  842.         for n in "0123456789":
  843.             key = "URL%s"%n
  844.             try: result=result + row % (key, html_quote(self[key])) 
  845.             except KeyError: pass
  846.         for n in "0123456789":
  847.             key = "BASE%s"%n
  848.             try: result=result + row % (key, html_quote(self[key])) 
  849.             except KeyError: pass
  850.  
  851.         result=result+"</table><h3>environ</h3><table>"
  852.         for k,v in self.environ.items():
  853.             if not hide_key(k):
  854.                 result=result + row % (html_quote(k), html_quote(v))
  855.         return result+"</table>"
  856.  
  857.     __repr__=__str__
  858.  
  859.     def _authUserPW(self):
  860.         global base64
  861.         auth=self._auth
  862.         if auth:
  863.             if lower(auth[:6]) == 'basic ':
  864.                 if base64 is None: import base64
  865.                 [name,password] = split(
  866.                     base64.decodestring(split(auth)[-1]), ':')
  867.                 return name, password
  868.  
  869.  
  870.  
  871.  
  872. base64=None
  873.  
  874. def sane_environment(env):
  875.     # return an environment mapping which has been cleaned of
  876.     # funny business such as REDIRECT_ prefixes added by Apache
  877.     # or HTTP_CGI_AUTHORIZATION hacks.
  878.     dict={}
  879.     for key, val in env.items():
  880.         while key[:9]=='REDIRECT_':
  881.             key=key[9:]
  882.         dict[key]=val
  883.     if dict.has_key('HTTP_CGI_AUTHORIZATION'):
  884.         dict['HTTP_AUTHORIZATION']=dict['HTTP_CGI_AUTHORIZATION']
  885.         try: del dict['HTTP_CGI_AUTHORIZATION']
  886.         except: pass
  887.     return dict
  888.  
  889.  
  890. # This is duplicated from DocumentTemplate.DT_Util to
  891. # prevent a dependency on the DocumentTemplate package.
  892. # Some folks still use the ZPublisher package as a
  893. # standalone publisher without DocumentTemplate.
  894. def html_quote(value, character_entities=(
  895.                       (('&'), '&'),
  896.                       (("<"), '<' ),
  897.                       ((">"), '>' ),
  898.                       (('"'), '"'))): #"
  899.         text=str(value)
  900.         for re, name in character_entities:
  901.             if find(text, re) >= 0: text=join(split(text, re), name)
  902.         return text
  903.  
  904.  
  905. def str_field(v):
  906.     if type(v) is ListType:
  907.         return map(str_field,v)
  908.  
  909.     if hasattr(v,'__class__') and v.__class__ is FieldStorage:
  910.         v=v.value
  911.     elif type(v) is not StringType:
  912.         if hasattr(v,'file') and v.file: v=v.file
  913.         elif hasattr(v,'value'): v=v.value
  914.     return v
  915.  
  916.  
  917. class FileUpload:
  918.     '''\
  919.     File upload objects
  920.  
  921.     File upload objects are used to represent file-uploaded data.
  922.  
  923.     File upload objects can be used just like files.
  924.  
  925.     In addition, they have a 'headers' attribute that is a dictionary
  926.     containing the file-upload headers, and a 'filename' attribute
  927.     containing the name of the uploaded file.
  928.     '''
  929.  
  930.     # Allow access to attributes such as headers and filename so
  931.     # that ZClass authors can use DTML to work with FileUploads.
  932.     __allow_access_to_unprotected_subobjects__=1
  933.  
  934.     def __init__(self, aFieldStorage):
  935.  
  936.         file=aFieldStorage.file
  937.         if hasattr(file, '__methods__'): methods=file.__methods__
  938.         else: methods= ['close', 'fileno', 'flush', 'isatty',
  939.                         'read', 'readline', 'readlines', 'seek',
  940.                         'tell', 'truncate', 'write', 'writelines']
  941.  
  942.         d=self.__dict__
  943.         for m in methods:
  944.             if hasattr(file,m): d[m]=getattr(file,m)
  945.  
  946.         self.headers=aFieldStorage.headers
  947.         self.filename=aFieldStorage.filename
  948.     
  949.  
  950. parse_cookie_lock=allocate_lock()
  951. def parse_cookie(text,
  952.                  result=None,
  953.                  qparmre=regex.compile(
  954.                      '\([\0- ]*'
  955.                      '\([^\0- ;,=\"]+\)="\([^"]*\)\"'
  956.                      '\([\0- ]*[;,]\)?[\0- ]*\)'
  957.                      ),
  958.                  parmre=regex.compile(
  959.                      '\([\0- ]*'
  960.                      '\([^\0- ;,=\"]+\)=\([^\0- ;,\"]*\)'
  961.                      '\([\0- ]*[;,]\)?[\0- ]*\)'
  962.                      ),
  963.                  acquire=parse_cookie_lock.acquire,
  964.                  release=parse_cookie_lock.release,
  965.                  ):
  966.  
  967.     if result is None: result={}
  968.     already_have=result.has_key
  969.  
  970.     acquire()
  971.     try:
  972.         if qparmre.match(text) >= 0:
  973.             # Match quoted correct cookies
  974.             name=qparmre.group(2)
  975.             value=qparmre.group(3)
  976.             l=len(qparmre.group(1))
  977.         elif parmre.match(text) >= 0:
  978.             # Match evil MSIE cookies ;)
  979.             name=parmre.group(2)
  980.             value=parmre.group(3)
  981.             l=len(parmre.group(1))
  982.         else:
  983.             # this may be an invalid cookie.
  984.             # We'll simply bail without raising an error
  985.             # if the cookie is invalid.
  986.             return result
  987.             
  988.     finally: release()
  989.  
  990.     if not already_have(name): result[name]=value
  991.  
  992.     return apply(parse_cookie,(text[l:],result))
  993.  
  994. # add class
  995. class record:
  996.  
  997.     # Allow access to record methods and values from DTML
  998.     __allow_access_to_unprotected_subobjects__=1
  999.  
  1000.     def __getattr__(self, key, default=None):
  1001.         if key in ('get', 'keys', 'items', 'values', 'copy', 'has_key'):
  1002.             return getattr(self.__dict__, key)
  1003.         raise AttributeError, key
  1004.  
  1005.     def __getitem__(self, key):
  1006.         return self.__dict__[key]
  1007.             
  1008.     def __str__(self):
  1009.         L1 = self.__dict__.items()
  1010.         L1.sort()
  1011.         return join(map(lambda item: "%s: %s" %item, L1), ", ") 
  1012.  
  1013.     __repr__ = __str__
  1014.  
  1015. # Flags
  1016. SEQUENCE=1
  1017. DEFAULT=2
  1018. RECORD=4
  1019. RECORDS=8
  1020. REC=RECORD|RECORDS
  1021. EMPTY=16
  1022. CONVERTED=32
  1023.  
  1024.